/*
 * Copyright 2010 Srikanth Reddy Lingala
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.lingala.zip4j.crypto;

import net.lingala.zip4j.crypto.engine.ZipCryptoEngine;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.exception.ZipExceptionConstants;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.util.InternalZipConstants;

public class StandardDecrypter implements IDecrypter {

    private FileHeader fileHeader;
    private byte[] crc = new byte[4];
    private ZipCryptoEngine zipCryptoEngine;

    public StandardDecrypter(FileHeader fileHeader, byte[] headerBytes) throws ZipException {
        if (fileHeader == null) {
            throw new ZipException("one of more of the input parameters were null in StandardDecryptor");
        }

        this.fileHeader = fileHeader;
        this.zipCryptoEngine = new ZipCryptoEngine();
        init(headerBytes);
    }

    public int decryptData(byte[] buff) throws ZipException {
        return decryptData(buff, 0, buff.length);
    }

    public int decryptData(byte[] buff, int start, int len) throws ZipException {
        if (start < 0 || len < 0) {
            throw new ZipException("one of the input parameters were null in standard decrpyt data");
        }

        try {
            for (int i = 0; i < len; i++) {
                int val = buff[i] & 0xff;
                val = (val ^ zipCryptoEngine.decryptByte()) & 0xff;
                zipCryptoEngine.updateKeys((byte) val);
                buff[i] = (byte) val;
            }
            return len;
        } catch (Exception e) {
            throw new ZipException(e);
        }
    }

    public void init(byte[] headerBytes) throws ZipException {
        byte[] crcBuff = fileHeader.getCrcBuff();
        crc[3] = (byte) (crcBuff[3] & 0xFF);
        crc[2] = (byte) ((crcBuff[3] >> 8) & 0xFF);
        crc[1] = (byte) ((crcBuff[3] >> 16) & 0xFF);
        crc[0] = (byte) ((crcBuff[3] >> 24) & 0xFF);

        if (crc[2] > 0 || crc[1] > 0 || crc[0] > 0)
            throw new IllegalStateException("Invalid CRC in File Header");

        if (fileHeader.getPassword() == null || fileHeader.getPassword().length <= 0) {
            throw new ZipException("Wrong password!", ZipExceptionConstants.WRONG_PASSWORD);
        }

        zipCryptoEngine.initKeys(fileHeader.getPassword());

        try {
            int result = headerBytes[0];
            for (int i = 0; i < InternalZipConstants.STD_DEC_HDR_SIZE; i++) {
//				Commented this as this check cannot always be trusted
//				New functionality: If there is an error in extracting a password protected file, 
//				"Wrong Password?" text is appended to the exception message
//				if(i+1 == InternalZipConstants.STD_DEC_HDR_SIZE && ((byte)(result ^ zipCryptoEngine.decryptByte()) != crc[3]) && !isSplit)
//					throw new ZipException("Wrong password!", ZipExceptionConstants.WRONG_PASSWORD);

                zipCryptoEngine.updateKeys((byte) (result ^ zipCryptoEngine.decryptByte()));
                if (i + 1 != InternalZipConstants.STD_DEC_HDR_SIZE)
                    result = headerBytes[i + 1];
            }
        } catch (Exception e) {
            throw new ZipException(e);
        }
    }

}
